#include "simplemedia/RtspClient.h"

namespace simplemedia
{

RTSPMediaClient::RTSPMediaClient()
    : loopthread_(nullptr)
    , stoploop_(false)
    , runstate_(false)
    , stream_status_update_handle_(nullptr)
    , on_data_receive_(nullptr)
    , format_ctx_(nullptr)
    , format_opts_(nullptr)
    , avctx_(nullptr)
    , packet_(nullptr)
    , timeout_(2000000)
    , maxdelay_(500000)
    , transportmethod_(RTSPMEDIACLIENT_TRANSPORT_TCP)
{
}

RTSPMediaClient::~RTSPMediaClient()
{
}

int RTSPMediaClient::Start(const std::string &source)
{
    source_ = source;
    if (source_.empty()) {
        return -1;
    }

    avformat_network_init();

    format_ctx_ = avformat_alloc_context();
    if (format_ctx_ == nullptr) {
        fprintf(stderr, "AVFormat context alloc failed!\n");
        goto error0;
    }

    avctx_ = avcodec_alloc_context3(nullptr);
    if (avctx_ == nullptr) {
        fprintf(stderr, "AVCodec context alloc failed\n");
        goto error1;
    }

    packet_ = av_packet_alloc();
    if (packet_ == nullptr) {
        goto error2;
    }

    loopthread_ = new std::thread(&RTSPMediaClient::LoopthreadHandle, this);
    if (loopthread_ == nullptr) {
        fprintf(stderr, "video stream loop thread create failed.\n");
        goto error3;
    }

    return 0;

error3:
    av_packet_free(&packet_);
error2:
    avcodec_free_context(&avctx_);
error1:
    avformat_free_context(format_ctx_);
    format_ctx_ = nullptr;
error0:
    avformat_network_deinit();
    return -1;
}

void RTSPMediaClient::Stop()
{
    if (format_ctx_ == nullptr)
        return;

    if (loopthread_) {
        stoploop_ = true;
        loopthread_->join();                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
        delete loopthread_;
        loopthread_ = nullptr;
    }

    av_packet_free(&packet_);
    avcodec_free_context(&avctx_);
    avformat_free_context(format_ctx_);
    format_ctx_ = nullptr;
    avformat_network_deinit();
}

void RTSPMediaClient::DataReceiveRegister(RTSPMediaClientDataReceiveCb cb)
{
    on_data_receive_ = cb;
}

void RTSPMediaClient::StatusUpdateHandleRegister(RTSPMediaClientStatusUpdateHandleCb cb)
{
    stream_status_update_handle_ = cb;
}

void RTSPMediaClient::OptionConfigTimeout(unsigned int us)
{
    timeout_ = us;
}

void RTSPMediaClient::OptionConfigMaxdelay(unsigned int us)
{
    maxdelay_ = us;
}

void RTSPMediaClient::OptionConfigTransportMethod(RTSPMediaClientTransportMethod method)
{
    transportmethod_ = method;
}

void RTSPMediaClient::LoopthreadHandle()
{
    int r;
    RTSPMediaClientResolution_t resolution;
    int videoindex;
    int errcnt;
    std::string rtsp_transport;

    AVStream *stream;
    AVCodecParameters *codecpar;

    runstate_ = true;

    for (;stoploop_ == false;) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));

        av_dict_set(&format_opts_, "timeout", std::to_string(timeout_).c_str(), 0);
        av_dict_set(&format_opts_, "max_delay", std::to_string(maxdelay_).c_str(), 0);
        av_dict_set(&format_opts_, "buffer_size", "1024000", 0);
        av_dict_set(&format_opts_, "thread_queue_size", "512", 0);
        switch (transportmethod_) {
        case RTSPMEDIACLIENT_TRANSPORT_UDP:
            rtsp_transport = "udp";
            break;
        case RTSPMEDIACLIENT_TRANSPORT_RTP:
            rtsp_transport = "rtp";
            break;
        case RTSPMEDIACLIENT_TRANSPORT_TCP:
            rtsp_transport = "tcp";
        default:
            break;
        }
        av_dict_set(&format_opts_, "rtsp_transport", rtsp_transport.c_str(), 0);

        r = avformat_open_input(&format_ctx_, source_.c_str(), nullptr, &format_opts_);
        if (r) {
            goto StatUpdate;
        }

        r = avformat_find_stream_info(format_ctx_, nullptr);
        if (r <  0) {
            goto ForNext;
        }

        videoindex = -1;
        for (size_t i = 0; i< format_ctx_->nb_streams; i++) {
            if (format_ctx_->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                videoindex = i;
                break;
            }
        }

        if (videoindex == -1) {
            goto ForNext;
        }

        stream = format_ctx_->streams[videoindex];
        codecpar = format_ctx_->streams[videoindex]->codecpar;

        r = avcodec_parameters_to_context(avctx_, codecpar);
        if (r) {
            goto ForNext;
        }

        resolution.denominator = stream->avg_frame_rate.den;
        resolution.numerator = stream->avg_frame_rate.num;
        resolution.horizontal = avctx_->width;
        resolution.vertical = avctx_->height;

        if (stream_status_update_handle_) {
            stream_status_update_handle_(true, resolution);
        }

        errcnt = 0;

        while (stoploop_ == false) {
            if (av_read_frame(format_ctx_, packet_) < 0) {
                fprintf(stderr, "av_read_frame error\n");
                if (errcnt++ >= 120) {
                    break;
                } else {
                    continue;
                }
            }

            errcnt = 0;

            // fprintf(stderr, "stream_index : %d, videoindex: %d, codecpar->codec_id: %d\n", 
            //     packet_->stream_index, videoindex, codecpar->codec_id);

            if (packet_->stream_index == videoindex) {
                if (codecpar->codec_id == AV_CODEC_ID_H264) {
                    if (on_data_receive_) {
                        on_data_receive_(packet_->size,
                                         packet_->data, 
                                        (packet_->flags & AV_PKT_FLAG_KEY) ? true : false);
                    }
                }
            }
            av_packet_unref(packet_);
        }

    ForNext:
        avformat_close_input(&format_ctx_);
    StatUpdate:
        resolution.denominator = 0;
        resolution.numerator = 0;
        resolution.horizontal = 0;
        resolution.vertical = 0;

        if (stream_status_update_handle_) {
            stream_status_update_handle_(false, resolution);
        }
    }

    runstate_ = false;
}

} // namespace simplemedia
